package com.itcoon.cloud.framework.security.provider;

import com.itcoon.cloud.framework.ex.Assertion;
import com.itcoon.cloud.framework.security.ex.SecurityResponseEnum;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;


public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	private final PasswordEncoder passwordEncoder;
	private volatile String userNotFoundEncodedPassword;
	private final UserDetailsService userDetailsService;
	private final UserDetailsPasswordService userDetailsPasswordService;

	public DaoAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
		this.passwordEncoder = passwordEncoder;
		this.userDetailsService = userDetailsService;
		this.userDetailsPasswordService = null;
	}

	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		Assertion.assertNotNull(authentication.getCredentials()).raise(SecurityResponseEnum.BAD_CREDENTIALS);
		String presentedPassword = authentication.getCredentials().toString();
		Assertion.assertTrue(this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())).raise(SecurityResponseEnum.BAD_CREDENTIALS);
	}

	protected void doAfterPropertiesSet() throws Exception {
		Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
	}

	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		this.prepareTimingAttackProtection();

		try {
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
			} else {
				return loadedUser;
			}
		} catch (AuthenticationException var4) {
			this.mitigateAgainstTimingAttack(authentication);
			throw var4;
		} catch (Exception var6) {
			throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
		}
	}

	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
		boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
		if (upgradeEncoding) {
			String presentedPassword = authentication.getCredentials().toString();
			String newPassword = this.passwordEncoder.encode(presentedPassword);
			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
		}
		return super.createSuccessAuthentication(principal, authentication, user);
	}

	private void prepareTimingAttackProtection() {
		if (this.userNotFoundEncodedPassword == null) {
			this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
		}

	}

	private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
		if (authentication.getCredentials() != null) {
			String presentedPassword = authentication.getCredentials().toString();
			this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
		}

	}

	protected PasswordEncoder getPasswordEncoder() {
		return this.passwordEncoder;
	}


	protected UserDetailsService getUserDetailsService() {
		return this.userDetailsService;
	}


}
